/*
    Copyright 2010 Northbranchlogic, Inc.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
 */

package com.norbl.runjrun;

import com.norbl.util.*;
import com.norbl.util.ec2.*;
import com.norbl.util.s3.*;
import com.norbl.util.sqs.*;
import com.norbl.util.sdb.*;
import com.xerox.amazonws.sdb.SDBException;
import org.jets3t.service.S3ServiceException;
import com.norbl.runjrun.gui.*;
import java.io.*;

/** <tt>Monitor</tt> maintains a periodically updated instance of
 *  {@link CurrentStates}. Optionally it creates {@link MonitorGui},
 *  which displays state data and supports user actions.
 *
 * @author Barnet Wagman
 */
public class Monitor {

    private Ec2Ops eops;
    private S3Ops sops;
    private SdbOps dops;
    private SqsOps qops;

    CurrentStates currentStates;

    long updateIntervalMillis;
    Updater updater;

    MonitorGui gui;
    StateTableModels stateTableModels;
    Actions actions;

        /** This constructor useses default values for all
         *  parameters. (See the other constructor for details.
         *  Default values are defined in {@link Constants}.
         *
         * @param awsAccessID
         * @param awsSecretKey
         * @param inputBucketName the name of the bucket that holds
         *  raw task objects.
         * @param createGui if true, the constructor creates a
         *  {@link MonitorGui} backed by this instance of <tt>Monitor</tt>.
         * @throws S3AccessException
         * @throws SdbAccessException
         */
    public Monitor(String awsAccessID,String awsSecretKey,
                   String inputBucketName,
                   boolean createGui)
        throws S3AccessException, SdbAccessException {
        this(awsAccessID,awsSecretKey,
             Constants.S3_RETRY_INTERVAL_MILLIS_DEFAULT,
             Constants.S3_MAX_WAIT_MILLIS_DEFAULT,
             Constants.SDB_RETRY_INTERVL_MILLIS_DEFAULT,
             Constants.SDB_MAX_WAIT_MILLIS_DEFAULT,
             Constants.TASKQUEUE_NAME_DEFAULT,
             Constants.MONITOR_UPDATE_INTERVAL_MILLIS_DEFAULT,
             true, // requeue orphans
             Constants.PULSE_INTER_MILLIS_DEFAULT,
             inputBucketName,
             Constants.INPUT_S3KEY_PREFIX_DEFAULT,
             createGui
            );
    }

        /**
         *
         * @param awsAccessID
         * @param awsSecretKey
         * @param s3RetryIntervalMillis the time interval (in milliseconds)
         *  between retries of failed S3 operations.
         * @param s3MaxWaitMillis the max total time (in milliseconds) which
         * may elapse while retries of failed S3 operations are attempted.
         * If this time is exceeded without a successful operation,
         * an exception is thrown.
         * @param sdbRetryIntervalMillis  the time interval (in milliseconds)
         *  between retries of failed SDB operations.
         * @param sdbMaxWaitMillis the max total time (in milliseconds) which
         * may elapse while retries of failed SDB operations are attempted.
         * If this time is exceeded without a successful operation,
         * an exception is thrown.
         * @param taskQueueName the name of the SQS queue that will hold
         *  task description messages.
         * @param updateIntervalMillis <tt>Monitor</tt> periodically updates
         *  node and task information stored in SDB; this param specifies the
         *  update interval in milliseconds.
         * @param requeueOrphanedTasks if true, <tt>Monitor</tt> will requeue
         *  'orphaned' tasks.  An 'orphan' is a task that started running on
         *  a compute node that failed before the task was complete. 'Requeing'
         *  entails marking changing the tasks status to 'waiting' and
         *  putting it back into the SQS task queue.
         * @param runnerNodePulseIntervalMillis  A {@link RunnerNode} periodically
         *  updates a 'pulse' field in it's SDB record. This variable defines
         *  the period (in milliseconds).
         * @param inputBucketName the name of the bucket that holds
         *  raw task objects.
         * @param inputS3KeyPrefix prefix for S3 keys of raw task objects
         *  stored in S3.
         * @param createGui
         * @throws S3AccessException
         * @throws SdbAccessException
         */
    public Monitor(String awsAccessID,String awsSecretKey,
                        long s3RetryIntervalMillis,
                        long s3MaxWaitMillis,
                        long sdbRetryIntervalMillis,
                        long sdbMaxWaitMillis,
                        String taskQueueName,
                        long updateIntervalMillis,
                        boolean requeueOrphanedTasks,
                        long runnerNodePulseIntervalMillis,
                        String inputBucketName,
                        String inputS3KeyPrefix,
                        boolean createGui)
        throws S3AccessException, SdbAccessException {

        try {

            System.setProperty("org.apache.commons.logging.Log",
                               "com.norbl.util.s3.NilLog");

            TaskInProgressWindow tip = null;
            if ( createGui ) {
                tip = TaskInProgressWindow.run("RunJRun Monitor",
                               "RunJRun Monitor",
                               "<html>The monitor is being initialized." +
                               "<br><br>" +
                               "Please wait.</html>");
            }
            
            this.updateIntervalMillis = updateIntervalMillis;

            eops = new Ec2Ops(awsAccessID, awsSecretKey);
            sops = new S3Ops(awsAccessID, awsSecretKey,
                             s3RetryIntervalMillis,s3MaxWaitMillis);

            dops = new SdbOps(awsAccessID, awsSecretKey,
                              sdbRetryIntervalMillis,sdbMaxWaitMillis);

            qops = new SqsOps(awsAccessID, awsSecretKey, taskQueueName);

            currentStates = new CurrentStates(eops, sops, dops, qops,
                                            requeueOrphanedTasks,
                                            runnerNodePulseIntervalMillis);

            updater = new Updater();
           
            (new Thread(updater)).start();

            if ( createGui ) {
                stateTableModels = new StateTableModels(currentStates);
                currentStates.addStateChangeListner(stateTableModels);
                actions = new Actions(eops, sops, dops, qops,
                                      currentStates.nodes,
                                      currentStates.tasks,
                                      inputBucketName, 
                                      inputS3KeyPrefix,
                                      stateTableModels);
                currentStates.tasks.setOps(actions);


                gui = new MonitorGui(stateTableModels, actions,
                                     awsAccessID, awsSecretKey, inputBucketName,
                                     this);                
                gui.startGui();
                tip.dispose();
            }
        }
        catch(S3ServiceException s3x) { throw new S3AccessException(s3x); }
        catch(SDBException sbx) { throw new SdbAccessException(sbx); }

    }

    public void stopUpdater() {
        updater.keepRunning = false;
    }

    public void shutdown() {
        stopUpdater();
        if ( gui != null ) gui.dispose();
    }

        // -------------------------------------------

    class Updater implements Runnable {

        boolean keepRunning;

        Updater() {}

        public void run() {            
            keepRunning = true;
            while ( keepRunning ) {               
                currentStates.update();
                Time.nap(updateIntervalMillis);               
            }
        }
    }

    public void update() {
        if ( currentStates != null ) currentStates.update();
    }

        // ----------------------------------------------

        /** Creates an instance of {@link Monitor} with default
         *  params that will launch a {@link MonitorGui}.<p>
         *  
         *  The first time this method is invoked, the user is prompted
         *  for AWS Access ID, AWS Secret Key, and a bucketname.
         *  This information will be stored (en clair) in a file named
         *  <tt>.runjrun_params.txt</tt> .  By default this file is written
         *  in the user's home directory (per Java system property user.home);
         *  A different directory may be specified. Please be sure that
         *  this directory is not publically accessible.
         */
    public static void launchWithGui() {

        MonitorParamHandler mph = new MonitorParamHandler();
        if ( mph.getParams(null) ) {
            Monitor m = new Monitor(mph.awsAccessID,
                                    mph.awsSecretKey,
                                    mph.bucketName,
                                    true);
        }
        else System.exit(0);
    }

    public static void launchWithGui(File paramFile) {

        MonitorParamHandler mph = new MonitorParamHandler();
        if ( mph.getParams(null,paramFile) ) {
            Monitor m = new Monitor(mph.awsAccessID,
                                    mph.awsSecretKey,
                                    mph.bucketName,
                                    true);
        }
        else {
            GuiUtil.notice(new Object[] {
                            "Param file ",
                            "    " + paramFile.getPath(),
                            " does not exist; we'll try the default."},
                           "Param file does not exist");
            launchWithGui();
        }
    }

        // ...........................................


        /** Invokes {@link #launchWithGui()}.
         *  <p>
         *  With no arguments, the default param file is used.  If it does
         *  not exist, the user is prompted for the information needed
         *  to create it.<p>
         * 
         *  The param file's full path may be specified as argv[0].
         *  If the file is does not exist, the default file is used or none
         *  exists, the user is prompted for the information needed
         *  to create it.
         *
         * @param argv the full path to the param file; for the default param
         *  file, omit args.
         * @throws Exception
         */
    public static void main(String[] argv) throws Exception {
      
        if (argv.length <= 0 ) launchWithGui();
        else {
            File f = new File(argv[0]);
            launchWithGui(f);
        }
    }   
}
